1 module hip.api.data.font;
2 public import hip.api.renderer.texture;
3 
4 
5 alias HipCharKerning = int[dchar];
6 alias HipFontKerning = HipCharKerning[dchar];
7 ///see hip.graphics.g2d.textrenderer
8 struct HipTextRendererVertexAPI
9 {
10     float[3] vPosition = [0,0,0];
11     float[2] vTexST = [0,0];
12 }
13 
14 /** 
15  *  A text information that is returned from the word wrap range.
16  *  Beyond the information with line and width, it also has a cache.
17  *  This cache is used for optimization in both kerning and associative array
18  *  lookup. This can make a big difference by having a single lookup instead of
19  *  2. The lookup is the slowest part of text rendering, which makes this a lot faster.
20  */
21 struct HipLineInfo
22 {
23     dstring line;
24     int width;
25     const(HipFontChar)*[512] fontCharCache;
26     int[512] kerningCache;
27 }
28 
29 struct HipWordWrapRange
30 {
31     private dstring inputText;
32     private IHipFont font;
33     private int maxWidth, currIndex;
34     private HipLineInfo currLine;
35     private bool hasFinished;
36 
37     this(dstring inputText, IHipFont font, int maxWidth)
38     {
39         this.inputText = inputText;
40         this.font = font;
41         this.maxWidth = maxWidth <= 0 ? int.max : maxWidth;
42     }
43 
44     bool empty(){return hasFinished;}
45     /** 
46      * Every time it pops the front, it will search for words. Words are defined as text delimited
47      * by spaces. If a word is bigger than the max width, it will be cutten and the word will spam
48      * through multiple lines. 
49      */
50     void popFront()
51     {
52         const(HipFontChar)* ch, next;
53         int currWidth = 0, wordWidth = 0, wordStartIndex = currIndex;
54         for(int i = currIndex, it = 0; i < inputText.length; i++, it++)
55         {
56             if(ch is null)
57             {
58                 ch = inputText[i] in font.characters;
59                 if(ch is null)
60                 {
61                     currLine.kerningCache[it] = 0;
62                     currLine.fontCharCache[it] = null;
63                     continue;
64                 }
65             }
66             int kern = 0;
67             if(i + 1 < inputText.length)
68             {
69                 next = inputText[i+1] in font.characters;
70                 if(next) kern = font.getKerning(ch, next);
71             }
72             currLine.kerningCache[it] = kern;
73             currLine.fontCharCache[it] = ch;
74             switch(inputText[i])
75             {
76                 case '\n':
77                     currLine.line = inputText[currIndex..i];
78                     currLine.width = currWidth;
79                     currIndex = i+1;
80                     return;
81                 case ' ':
82                     if(font.spaceWidth + wordWidth + currWidth > maxWidth)
83                     {
84                         currLine.line = inputText[currIndex..i];
85                         currLine.width = currWidth+wordWidth;
86                         currIndex = i+1;
87                         return;
88                     }
89                     else
90                     {
91                         currWidth+= wordWidth + font.spaceWidth;
92                         wordStartIndex = i;
93                         wordWidth = 0;
94                     }
95                     break;
96                 default:
97                     if(wordWidth + ch.xadvance + kern + currWidth > maxWidth)
98                     {
99                         if(wordStartIndex == currIndex)
100                         {
101                             currWidth = wordWidth;
102                             wordStartIndex = i;
103                         }
104                         ///Subtract one for ignoring the space.
105                         currLine.line = inputText[currIndex..wordStartIndex];
106                         currLine.width = currWidth;
107                         if(wordStartIndex < inputText.length && inputText[wordStartIndex] == ' ')
108                             wordStartIndex++;
109                         currIndex = wordStartIndex;
110                         return;
111                     }
112                     wordWidth += ch.xadvance + kern;
113                     break;
114             }
115             ch = next;
116         }
117         if(currIndex < inputText.length && inputText[currIndex] == ' ')
118             currIndex++;
119         currLine.line = inputText[currIndex..$];
120         currLine.width = currWidth+wordWidth;
121         currIndex = cast(int)inputText.length;
122         if(currLine.line.length == 0) hasFinished = true;
123     }
124 
125     HipLineInfo front()
126     {
127         if(currIndex == 0) popFront();
128         return currLine;
129     }
130 }
131 
132 struct HipFontChar
133 {
134     uint id;
135     ///Those are in absolute values
136     int x, y, width, height;
137 
138     int xoffset, yoffset, xadvance, page, chnl;
139 
140     ///Normalized values
141     float normalizedX, normalizedY, normalizedWidth, normalizedHeight;
142     int glyphIndex;
143     void putCharacterQuad(float x, float y, float depth, HipTextRendererVertexAPI[] quad) const
144     {
145         //Gen vertices 
146         //Top left
147         quad[0] = HipTextRendererVertexAPI(
148             [x, y, depth],
149             [normalizedX, normalizedY] //ST
150         );
151         //Top Right
152         quad[1] = HipTextRendererVertexAPI(
153             [x+width, y,depth],
154             [normalizedX + normalizedWidth, normalizedY] //S + Wnorm, T
155         );
156         //Bot right
157         quad[2] = HipTextRendererVertexAPI(
158             [x+ width, y +height, depth],
159             [
160                 normalizedX + normalizedWidth, //S+Wnorm
161                 normalizedY + normalizedHeight //T+Hnorm
162             ]
163         );
164         //Bot left
165         quad[3] = HipTextRendererVertexAPI(
166             [x, y + height, depth],
167             [normalizedX, normalizedY + normalizedHeight] // S, T+Hnorm
168         );
169     }
170 }
171 
172 interface IHipFont
173 {
174     int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const;
175     int getKerning(dchar current, dchar next) const;
176     /** 
177      * 
178      * Params:
179      *   text = The text
180      *   linesWidths = Save width per line
181      *   biggestWidth = The biggest width in lines
182      *   height = Height of all the lines together
183      *   maxWidth = If maxWidth != -1, it will break the text into lines automatically. 
184      */
185     void calculateTextBounds(in dstring text, ref uint[] linesWidths, out int biggestWidth, out int height, int maxWidth = -1) const;
186     HipWordWrapRange wordWrapRange(dstring text, int maxWidth) const;
187     ref HipFontChar[dchar] characters();
188     ref IHipTexture texture();
189     uint spaceWidth() const;
190     uint spaceWidth(uint newWidth);
191     uint lineBreakHeight() const;
192     uint lineBreakHeight(uint newHeight);
193 
194 }
195 
196 abstract class HipFont : IHipFont
197 {
198     
199     abstract int getKerning(dchar current, dchar next) const;
200     abstract int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const;
201 
202     ///Underlying GPU texture
203     IHipTexture _texture;
204     HipFontChar[dchar] _characters;
205     ///Saves the space width for the bitmap text process the ' '. If the original spaceWidth is == 0, it won't draw a quad
206     uint _spaceWidth;
207     ///How much the line break will offset in Y the next char
208     uint _lineBreakHeight;
209 
210     ///////Properties///////
211     final ref HipFontChar[dchar] characters(){return _characters;}
212     final ref const(HipFontChar[dchar]) characters() const {return _characters;}
213     final ref IHipTexture texture(){return _texture;}
214     final uint spaceWidth() const {return _spaceWidth;}
215     final uint spaceWidth(uint newWidth){return _spaceWidth = newWidth;}
216     final uint lineBreakHeight() const {return _lineBreakHeight;}
217     final uint lineBreakHeight(uint newHeight){return _lineBreakHeight = newHeight;}
218 
219     final HipWordWrapRange wordWrapRange(dstring text, int maxWidth) const
220     {
221         return HipWordWrapRange(text, cast(IHipFont)this, maxWidth);
222     }
223     
224 
225     final void calculateTextBounds(in dstring text, ref uint[] linesWidths, out int biggestWidth, out int height, int maxWidth = -1) const
226     {
227         int i = 0;
228         foreach(HipLineInfo lineInfo; wordWrapRange(text, maxWidth))
229         {
230             if(lineInfo.width > biggestWidth) biggestWidth = lineInfo.width;
231             if(linesWidths.length < i+1)
232                 linesWidths.length++;
233             linesWidths[i++] = lineInfo.width;
234         }
235         height = lineBreakHeight*i;
236     }
237     HipFont getFontWithSize(uint size);
238 
239 }